package rejoin

import (
	"context"
	"encoding/hex"
	"github.com/brocaar/lorawan"
	"github.com/brocaar/lorawan/backend"
	"github.com/pkg/errors"
	"network/v3/internal/config"
	"network/v3/internal/models"
	"network/v3/internal/storage"
)

// ErrAbort is used to abort the flow without error
var ErrAbort = errors.New("nothing to do")

type rejoinContext struct {
	ctx context.Context

	RXPacket   models.RXPacket
	RejoinType lorawan.JoinType
	RJCount    uint16

	NetID   lorawan.NetID
	DevEUI  lorawan.EUI64
	JoinEUI lorawan.EUI64

	Device         storage.Device
	ServiceProfile storage.ServiceProfile
	DeviceProfile  storage.DeviceProfile
	DeviceSession  storage.DeviceSession

	DevAddr lorawan.DevAddr

	RejoinAnsPayload backend.RejoinAnsPayload
}

var (
	rx2DR       int
	rx1DROffset int
	rx1Delay    int
	keks        map[string][]byte
	netID       lorawan.NetID
)

// Setup configures the package.
func Setup(conf config.Config) error {
	keks = make(map[string][]byte)

	nsServer := conf.NetworkServer
	netID = nsServer.NetID
	rx2DR = nsServer.NetworkSettings.RX2DR
	rx1DROffset = nsServer.NetworkSettings.RX1DROffset
	rx1Delay = nsServer.NetworkSettings.RX1Delay

	for _, k := range conf.JoinServer.KEK.Set {
		kek, err := hex.DecodeString(k.KEK)
		if err != nil {
			return errors.Wrap(err, "decode kek error")
		}

		keks[k.Label] = kek
	}

	return nil
}

// Handle handles a rejoin-request.
func Handle(ctx context.Context, rxPacket models.RXPacket) error {
	rjctx := rejoinContext{
		ctx:      ctx,
		RXPacket: rxPacket,
	}

	for _, t := range tasks {
		if err := t(&rjctx); err != nil {
			if err == ErrAbort {
				return nil
			}
			return err
		}
	}

	return nil
}

func forRejoinType(types []lorawan.JoinType, tasks ...func(*rejoinContext) error) func(*rejoinContext) error {
	return func(ctx *rejoinContext) error {
		for _, t := range types {
			if ctx.RejoinType == t {
				for _, f := range tasks {
					if err := f(ctx); err != nil {
						return err
					}
				}
			}
		}

		return nil
	}
}
